{
  "cells": [
    {
      "cell_type": "markdown",
      "id": "1331faa1",
      "metadata": {},
      "source": [
        "You can order print and ebook versions of *Think Python 3e* from\n",
        "[Bookshop.org](https://bookshop.org/a/98697/9781098155438) and\n",
        "[Amazon](https://www.amazon.com/_/dp/1098155432?smid=ATVPDKIKX0DER&_encoding=UTF8&tag=oreilly20-20&_encoding=UTF8&tag=greenteapre01-20&linkCode=ur2&linkId=e2a529f94920295d27ec8a06e757dc7c&camp=1789&creative=9325).\n",
        "\n",
        "您可以订购的印刷版和电子书版本 *想想Python 3e* 从\n",
        " [Bookshop.org](https://bookshop.org/a/98697/9781098155438) 和\n",
        " [Amazon](https://www.amazon.com/_/dp/1098155432?smid=ATVPDKIKX0DER&_encoding=UTF8&tag=oreilly20-20&_encoding=UTF8&tag=greenteapre01-20&linkCode=ur2&linkId=e2a529f94920295d27ec8a06e757dc7c&camp=1789&creative=9325) 。\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 1,
      "id": "ae5a86f8",
      "metadata": {
        "tags": []
      },
      "outputs": [],
      "source": [
        "from os.path import basename, exists\n",
        "\n",
        "def download(url):\n",
        "    filename = basename(url)\n",
        "    if not exists(filename):\n",
        "        from urllib.request import urlretrieve\n",
        "\n",
        "        local, _ = urlretrieve(url, filename)\n",
        "        print(\"Downloaded \" + str(local))\n",
        "    return filename\n",
        "\n",
        "download('https://github.com/AllenDowney/ThinkPython/raw/v3/thinkpython.py');\n",
        "download('https://github.com/AllenDowney/ThinkPython/raw/v3/diagram.py');\n",
        "download('https://github.com/ramalho/jupyturtle/releases/download/2024-03/jupyturtle.py');\n",
        "\n",
        "import thinkpython"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "e826e661",
      "metadata": {},
      "source": [
        "# 第16章:类和对象（Classes and Objects）\n",
        "\n",
        "At this point we have defined classes and created objects that represent the time of day and the day of the year.\n",
        "And we've defined methods that create, modify, and perform computations with these objects.\n",
        "\n",
        "在这一点上，我们已经定义了类并创建了表示一天中的时间和一年中的日期的对象。\n",
        "我们已经定义了用这些对象创建、修改和执行计算的方法。\n",
        "\n",
        "In this chapter we'll continue our tour of object-oriented programming (OOP) by defining classes that represent geometric objects, including points, lines, rectangles, and circles.\n",
        "We'll write methods that create and modify these objects, and we'll use the `jupyturtle` module to draw them.\n",
        "\n",
        "在本章中，我们将通过定义表示几何对象 (包括点、线、矩形和圆) 的类来继续我们的面向对象编程 (OOP) 之旅。\n",
        "我们将编写创建和修改这些对象的方法，我们将使用 `jupyturtle` 模块来绘制它们。\n",
        "\n",
        "I'll use these classes to demonstrate OOP topics including object identity and equivalence, shallow and deep copying, and polymorphism.\n",
        "\n",
        "我将使用这些类来演示OOP主题，包括对象标识和等效性，浅层和深层复制以及多态性。\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "6b414d4a",
      "metadata": {
        "tags": []
      },
      "source": [
        "## 创建点（Creating a Point）\n",
        "\n",
        "In computer graphics a location on the screen is often represented using a pair of coordinates in an `x`-`y` plane.\n",
        "By convention, the point `(0, 0)` usually represents the upper-left corner of the screen, and  `(x, y)` represents the point `x` units to the right and `y` units down from the origin.\n",
        "Compared to the Cartesian coordinate system you might have seen in a math class, the `y` axis is upside-down.\n",
        "\n",
        "在计算机图形学中，屏幕上的位置通常使用 `x` - `y` 飞机。\n",
        "按照惯例，这一点 `(0, 0)` 通常表示屏幕的左上角，并且 `(x, y)` 表示点 `x` 右侧的单位和 `y` 从原点向下的单位。\n",
        "与您可能在数学课上看到的笛卡尔坐标系相比， `y` 轴是颠倒的。\n",
        "\n",
        "There are several ways we might represent a point in Python:\n",
        "\n",
        "有几种方法可以在Python中表示一个点:\n",
        "\n",
        "-   We can store the coordinates separately in two variables, `x` and `y`.\n",
        "\n",
        "- 我们可以将坐标分别存储在两个变量中， `x` 和 `y` 。\n",
        "\n",
        "-   We can store the coordinates as elements in a list or tuple.\n",
        "\n",
        "- 我们可以将坐标存储为列表或元组中的元素。\n",
        "\n",
        "-   We can create a new type to represent points as objects.\n",
        "\n",
        "- 我们可以创建一个新的类型来将点表示为对象。\n",
        "\n",
        "In object-oriented programming, it would be most idiomatic to create a new type.\n",
        "To do that, we'll start with a class definition for `Point`.\n",
        "\n",
        "在面向对象编程中，创建新类型是最惯用的。\n",
        "为此，我们将从类定义开始 `Point` 。\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 2,
      "id": "c9c99d2c",
      "metadata": {},
      "outputs": [],
      "source": [
        "class Point:\n",
        "    \"\"\"Represents a point in 2-D space.\"\"\"\n",
        "    \n",
        "    def __init__(self, x, y):\n",
        "        self.x = x\n",
        "        self.y = y\n",
        "        \n",
        "    def __str__(self):\n",
        "        return f'Point({self.x}, {self.y})'"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "3d35a095",
      "metadata": {},
      "source": [
        "The `__init__` method takes the coordinates as parameters and assigns them to attributes `x` and `y`.\n",
        "The `__str__` method returns a string representation of the `Point`.\n",
        "\n",
        "的 `__init__` 方法将坐标作为参数并将其分配给属性 `x` 和 `y` 。\n",
        "的 `__str__` 方法返回的字符串表示形式 `Point` 。\n",
        "\n",
        "Now we can instantiate and display a `Point` object like this.\n",
        "\n",
        "现在我们可以实例化并显示一个 `Point` 像这样的对象。\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 3,
      "id": "d318001a",
      "metadata": {},
      "outputs": [],
      "source": [
        "start = Point(0, 0)\n",
        "print(start)"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "b3fd8858",
      "metadata": {},
      "source": [
        "The following diagram shows the state of the new object. \n",
        "\n",
        "下图显示了新对象的状态。\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 4,
      "id": "3eb47826",
      "metadata": {
        "tags": []
      },
      "outputs": [],
      "source": [
        "from diagram import make_frame, make_binding\n",
        "\n",
        "d1 = vars(start)\n",
        "frame = make_frame(d1, name='Point', dy=-0.25, offsetx=0.18)\n",
        "binding = make_binding('start', frame)"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 5,
      "id": "6702a353",
      "metadata": {
        "tags": []
      },
      "outputs": [],
      "source": [
        "from diagram import diagram, adjust\n",
        "\n",
        "width, height, x, y = [1.41, 0.89, 0.26, 0.5]\n",
        "ax = diagram(width, height)\n",
        "bbox = binding.draw(ax, x, y)\n",
        "#adjust(x, y, bbox)"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "713b7410",
      "metadata": {},
      "source": [
        "As usual, a programmer-defined type is represented by a box with the name of the type outside and the attributes inside.\n",
        "\n",
        "像往常一样，程序员定义的类型由一个框表示，该框的名称位于外部，属性位于内部。\n",
        "\n",
        "In general, programmer-defined types are mutable, so we can write a method like `translate` that takes two numbers, `dx` and `dy`, and adds them to the attributes `x` and `y`.\n",
        "\n",
        "一般来说，程序员定义的类型是可变的，所以我们可以编写一个像 `translate` 这需要两个数字， `dx` 和 `dy` ,并将它们添加到属性 `x` 和 `y` 。\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 6,
      "id": "0f710c4f",
      "metadata": {},
      "outputs": [],
      "source": [
        "%%add_method_to Point\n",
        "\n",
        "    def translate(self, dx, dy):\n",
        "        self.x += dx\n",
        "        self.y += dy"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "4d183292",
      "metadata": {},
      "source": [
        "This function translates the `Point` from one location in the plane to another.\n",
        "If we don't want to modify an existing `Point`, we can use `copy` to copy the original object and then modify the copy.\n",
        "\n",
        "此函数将 `Point` 从飞机上的一个位置到另一个位置。\n",
        "如果我们不想修改现有的 `Point` ,我们可以使用 `copy` 以复制原始对象，然后修改副本。\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 7,
      "id": "6e37126b",
      "metadata": {},
      "outputs": [],
      "source": [
        "from copy import copy\n",
        "\n",
        "end1 = copy(start)\n",
        "end1.translate(300, 0)\n",
        "print(end1)"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "562567c2",
      "metadata": {},
      "source": [
        "We can encapsulate those steps in another method called `translated`.\n",
        "\n",
        "我们可以将这些步骤封装在另一个名为 `translated` 。\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 8,
      "id": "f38bfaaa",
      "metadata": {},
      "outputs": [],
      "source": [
        "%%add_method_to Point\n",
        "\n",
        "    def translated(self, dx=0, dy=0):\n",
        "        point = copy(self)\n",
        "        point.translate(dx, dy)\n",
        "        return point"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "a7a635ee",
      "metadata": {},
      "source": [
        "In the same way that the built in function `sort` modifies a list, and the `sorted` function creates a new list, now we have a `translate` method that modifies a `Point` and a `translated` method that creates a new one.\n",
        "\n",
        "以同样的方式，内置的函数 `sort` 修改列表，并且 `sorted` 函数创建一个新的列表，现在我们有一个 `translate` 方法，该方法修改 `Point` 和a `translated` 方法创建一个新的.\n",
        "\n",
        "Here's an example:\n",
        "\n",
        "这里有一个例子:\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 9,
      "id": "cef864a6",
      "metadata": {},
      "outputs": [],
      "source": [
        "end2 = start.translated(0, 150)\n",
        "print(end2)"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "923362d2",
      "metadata": {},
      "source": [
        "In the next section, we'll use these points to define and draw a line.\n",
        "\n",
        "在下一节中，我们将使用这些点来定义和绘制一条线。\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "837f98fd",
      "metadata": {},
      "source": [
        "## 创建一条线（Creating a Line）\n",
        "\n",
        "Now let's define a class that represents the line segment between two points.\n",
        "As usual, we'll start with an `__init__` method and a `__str__` method.\n",
        "\n",
        "现在让我们定义一个表示两点之间线段的类。\n",
        "像往常一样，我们将从一个 `__init__` 方法和一个 `__str__` 方法。\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 10,
      "id": "6ae012a1",
      "metadata": {},
      "outputs": [],
      "source": [
        "class Line:\n",
        "    def __init__(self, p1, p2):\n",
        "        self.p1 = p1\n",
        "        self.p2 = p2\n",
        "        \n",
        "    def __str__(self):\n",
        "        return f'Line({self.p1}, {self.p2})'"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "d7dad30e",
      "metadata": {},
      "source": [
        "With those two methods, we can instantiate and display a `Line` object we'll use to represent the `x` axis.\n",
        "\n",
        "使用这两种方法，我们可以实例化并显示一个 `Line` 对象，我们将使用它来表示 `x` 轴。\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 11,
      "id": "39b2ae2a",
      "metadata": {},
      "outputs": [],
      "source": [
        "line1 = Line(start, end1)\n",
        "print(line1)"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "e7b5fd9a",
      "metadata": {},
      "source": [
        "When we call `print` and pass `line` as a parameter, `print` invokes `__str__` on `line`.\n",
        "The `__str__` method uses an f-string to create a string representation of the `line`. \n",
        "\n",
        "当我们调用 `print` 并通过 `line` 作为参数， `print` 调用 `__str__` 开 `line` 。\n",
        "的 `__str__` 方法使用f字符串创建的字符串表示形式 `line` 。\n",
        "\n",
        "The f-string contains two expressions in curly braces, `self.p1` and `self.p2`.\n",
        "When those expressions are evaluated, the results are `Point` objects.\n",
        "Then, when they are converted to strings, the `__str__` method from the `Point` class gets invoked.\n",
        "\n",
        "f字符串在大括号中包含两个表达式， `self.p1` 和 `self.p2` 。\n",
        "当这些表达式被求值时，结果是 `Point` 对象。\n",
        "然后，当它们转换为字符串时， `__str__` 方法从 `Point` 类被调用。\n",
        "\n",
        "That's why, when we display a `Line`, the result contains the string representations of the `Point` objects.\n",
        "\n",
        "这就是为什么，当我们显示 `Line` ,结果包含的字符串表示形式 `Point` 对象。\n",
        "\n",
        "The following object diagram shows the state of this `Line` object.\n",
        "\n",
        "下面的对象图显示了此 `Line` 对象。\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 12,
      "id": "dee93202",
      "metadata": {
        "tags": []
      },
      "outputs": [],
      "source": [
        "from diagram import Binding, Value, Frame\n",
        "\n",
        "d1 = vars(line1.p1)\n",
        "frame1 = make_frame(d1, name='Point', dy=-0.25, offsetx=0.17)\n",
        "\n",
        "d2 = vars(line1.p2)\n",
        "frame2 = make_frame(d2, name='Point', dy=-0.25, offsetx=0.17)\n",
        "\n",
        "binding1 = Binding(Value('start'), frame1, dx=0.4)\n",
        "binding2 = Binding(Value('end'), frame2, dx=0.4)\n",
        "frame3 = Frame([binding1, binding2], name='Line', dy=-0.9, offsetx=0.4, offsety=-0.25)\n",
        "\n",
        "binding = make_binding('line1', frame3)"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 13,
      "id": "43682c57",
      "metadata": {
        "tags": []
      },
      "outputs": [],
      "source": [
        "width, height, x, y = [2.45, 2.12, 0.27, 1.76]\n",
        "ax = diagram(width, height)\n",
        "bbox = binding.draw(ax, x, y)\n",
        "#adjust(x, y, bbox)"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "079859b5",
      "metadata": {},
      "source": [
        "String representations and object diagrams are useful for debugging, but the point of this example is to generate graphics, not text!\n",
        "So we'll use the `jupyturtle` module to draw lines on the screen.\n",
        "\n",
        "字符串表示和对象图对于调试很有用，但是这个例子的重点是生成图形，而不是文本!\n",
        "所以我们将使用 `jupyturtle` 模块在屏幕上画线。\n",
        "\n",
        "As we did in [Chapter 4](section_turtle_module), we'll use `make_turtle` to create a `Turtle` object and a small canvas where it can draw.\n",
        "To draw lines, we'll use two new functions from the `jupyturtle` module:\n",
        "\n",
        "正如我们在 [Chapter 4](section_turtle_module) ,我们将使用 `make_turtle` 要创建一个 `Turtle` 对象和一个可以绘制的小画布。\n",
        "为了绘制线条，我们将使用来自 `jupyturtle` 模块:\n",
        "\n",
        "* `jumpto`, which takes two coordinates and moves the `Turtle` to the given location without drawing a line, and \n",
        "\n",
        " *  `jumpto` ,它采用两个坐标并移动 `Turtle` 到给定的位置而不画线，并且\n",
        "\n",
        "* `moveto`, which moves the `Turtle` from its current location to the given location, and draws a line segment between them.\n",
        "\n",
        " *  `moveto` ,这会移动 `Turtle` 从其当前位置到给定位置，并在它们之间绘制线段。\n",
        "\n",
        "Here's how we import them.\n",
        "\n",
        "这就是我们如何导入它们。\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 14,
      "id": "5225d870",
      "metadata": {},
      "outputs": [],
      "source": [
        "from jupyturtle import make_turtle, jumpto, moveto"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "9d2dd88f",
      "metadata": {},
      "source": [
        "And here's a method that draws a `Line`.\n",
        "\n",
        "这里有一个方法，绘制一个 `Line` 。\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 15,
      "id": "999843cb",
      "metadata": {},
      "outputs": [],
      "source": [
        "%%add_method_to Line\n",
        "\n",
        "    def draw(self):\n",
        "        jumpto(self.p1.x, self.p1.y)\n",
        "        moveto(self.p2.x, self.p2.y)"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "2341f0e0",
      "metadata": {},
      "source": [
        "To show how it's used, I'll create a second line that represents the `y` axis.\n",
        "\n",
        "为了展示它的使用方式，我将创建第二行，表示 `y` 轴。\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 16,
      "id": "6ac10ec7",
      "metadata": {},
      "outputs": [],
      "source": [
        "line2 = Line(start, end2)\n",
        "print(line2)"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "d7450736",
      "metadata": {},
      "source": [
        "And then draw the axes.\n",
        "\n",
        "然后绘制坐标轴。\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 17,
      "id": "b15b70dd",
      "metadata": {},
      "outputs": [],
      "source": [
        "make_turtle()\n",
        "line1.draw()\n",
        "line2.draw()"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "473c156f",
      "metadata": {},
      "source": [
        "As we define and draw more objects, we'll use these lines again.\n",
        "But first let's talk about object equivalence and identity.\n",
        "\n",
        "当我们定义和绘制更多对象时，我们将再次使用这些线。\n",
        "但首先让我们谈谈对象等价和身份。\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "950da673",
      "metadata": {},
      "source": [
        "## 等价性和同一性（Equivalence and identity）\n",
        "\n",
        "Suppose we create two points with the same coordinates.\n",
        "\n",
        "假设我们创建具有相同坐标的两个点。\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 18,
      "id": "62414805",
      "metadata": {},
      "outputs": [],
      "source": [
        "p1 = Point(200, 100)\n",
        "p2 = Point(200, 100)"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "82b14526",
      "metadata": {},
      "source": [
        "If we use the `==` operator to compare them, we get the default behavior for programmer-defined types -- the result is `True` only if they are the same object, which they are not.\n",
        "\n",
        "如果我们使用 `==` 运算符来比较它们，我们得到程序员定义的类型的默认行为-结果是 `True` 只有当它们是同一个对象时，它们不是。\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 19,
      "id": "7c611eb2",
      "metadata": {},
      "outputs": [],
      "source": [
        "p1 == p2"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "96be0ff8",
      "metadata": {},
      "source": [
        "If we want to change that behavior, we can provide a special method called `__eq__` that defines what it means for two `Point` objects to be equal.\n",
        "\n",
        "如果我们想改变这种行为，我们可以提供一个特殊的方法，称为 `__eq__` 这定义了它对两个 `Point` 对象相等。\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 20,
      "id": "3a976072",
      "metadata": {},
      "outputs": [],
      "source": [
        "%%add_method_to Point\n",
        "\n",
        "def __eq__(self, other):\n",
        "    return (self.x == other.x) and (self.y == other.y)"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "7f4409de",
      "metadata": {},
      "source": [
        "This definition considers two `Points` to be equal if their attributes are equal.\n",
        "Now when we use the `==` operator, it invokes the `__eq__` method, which indicates that `p1` and `p2` are considered equal.\n",
        "\n",
        "这个定义考虑了两个 `Points` 如果它们的属性相等，则相等。\n",
        "现在，当我们使用 `==` 运算符，它会调用 `__eq__` 方法，该方法指示 `p1` 和 `p2` 被认为是平等的。\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 21,
      "id": "7660d756",
      "metadata": {},
      "outputs": [],
      "source": [
        "p1 == p2"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "52662e6a",
      "metadata": {},
      "source": [
        "But the `is` operator still indicates that they are different objects.\n",
        "\n",
        "但是 `is` 运算符仍指示它们是不同的对象。\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 22,
      "id": "e76ff9ef",
      "metadata": {},
      "outputs": [],
      "source": [
        "p1 is p2"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "c008d3dd",
      "metadata": {},
      "source": [
        "It's not possible to override the `is` operator -- it always checks whether the objects are identical.\n",
        "But for programmer-defined types, you can override the `==` operator so it checks whether the objects are equivalent.\n",
        "And you can define what equivalent means.\n",
        "\n",
        "不可能重写 `is` 运算符 -- 它总是检查对象是否相同。\n",
        "但对于程序员定义的类型，您可以重写 `==` 运算符，以便检查对象是否等效。\n",
        "你可以定义等效的含义。\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "893a8cab",
      "metadata": {},
      "source": [
        "## 创建一个矩形（Creating a Rectangle）\n",
        "\n",
        "Now let's define a class that represents and draws rectangles.\n",
        "To keep things simple, we'll assume that the rectangles are either vertical or horizontal, not at an angle.\n",
        "What attributes do you think we should use to specify the location and size of a rectangle?\n",
        "\n",
        "现在让我们定义一个表示和绘制矩形的类。\n",
        "为了简单，我们假设矩形是垂直的或水平的，而不是成角度的。\n",
        "您认为我们应该使用哪些属性来指定矩形的位置和大小？\n",
        "\n",
        "There are at least two possibilities:\n",
        "\n",
        "至少有两种可能性:\n",
        "\n",
        "-   You could specify the width and height of the rectangle and the location of one corner.\n",
        "\n",
        "- 您可以指定矩形的宽度和高度以及一个角的位置。\n",
        "\n",
        "-   You could specify two opposing corners.\n",
        "\n",
        "- 您可以指定两个相对的角。\n",
        "\n",
        "At this point it's hard to say whether either is better than the other, so let's implement the first one.\n",
        "Here is the class definition.\n",
        "\n",
        "在这一点上，很难说是否比另一个更好，所以让我们实现第一个。\n",
        "这里是类的定义。\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 23,
      "id": "c2a2aa29",
      "metadata": {},
      "outputs": [],
      "source": [
        "class Rectangle:\n",
        "    \"\"\"Represents a rectangle. \n",
        "\n",
        "    attributes: width, height, corner.\n",
        "    \"\"\"\n",
        "    def __init__(self, width, height, corner):\n",
        "        self.width = width\n",
        "        self.height = height\n",
        "        self.corner = corner\n",
        "        \n",
        "    def __str__(self):\n",
        "        return f'Rectangle({self.width}, {self.height}, {self.corner})'"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "df2852f3",
      "metadata": {},
      "source": [
        "As usual, the `__init__` method assigns the parameters to attributes and the `__str__` returns a string representation of the object.\n",
        "Now we can instantiate a `Rectangle` object, using a `Point` as the location of the upper-left corner.\n",
        "\n",
        "像往常一样， `__init__` 方法将参数分配给属性和 `__str__` 返回对象的字符串表示形式。\n",
        "现在我们可以实例化一个 `Rectangle` 对象，使用 `Point` 作为左上角的位置。\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 24,
      "id": "5d36d561",
      "metadata": {},
      "outputs": [],
      "source": [
        "corner = Point(30, 20)\n",
        "box1 = Rectangle(100, 50, corner)\n",
        "print(box1)"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "a9e0b5ec",
      "metadata": {},
      "source": [
        "The following diagram shows the state of this object.\n",
        "\n",
        "下图显示了此对象的状态。\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 25,
      "id": "6e47d99a",
      "metadata": {
        "tags": []
      },
      "outputs": [],
      "source": [
        "from diagram import Binding, Value\n",
        "\n",
        "def make_rectangle_binding(name, box, **options):\n",
        "    d1 = vars(box.corner)\n",
        "    frame_corner = make_frame(d1, name='Point', dy=-0.25, offsetx=0.07)\n",
        "\n",
        "    d2 = dict(width=box.width, height=box.height)\n",
        "    frame = make_frame(d2, name='Rectangle', dy=-0.25, offsetx=0.45)\n",
        "    binding = Binding(Value('corner'), frame1, dx=0.92, draw_value=False, **options)\n",
        "    frame.bindings.append(binding)\n",
        "\n",
        "    binding = Binding(Value(name), frame)\n",
        "    return binding, frame_corner\n",
        "\n",
        "binding_box1, frame_corner1 = make_rectangle_binding('box1', box1)"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 26,
      "id": "1e7e4a72",
      "metadata": {
        "tags": []
      },
      "outputs": [],
      "source": [
        "from diagram import Bbox\n",
        "\n",
        "width, height, x, y = [2.83, 1.49, 0.27, 1.1]\n",
        "ax = diagram(width, height)\n",
        "bbox1 = binding_box1.draw(ax, x, y)\n",
        "bbox2 = frame_corner1.draw(ax, x+1.85, y-0.6)\n",
        "bbox = Bbox.union([bbox1, bbox2])\n",
        "#adjust(x, y, bbox)"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "bb54e6b5",
      "metadata": {},
      "source": [
        "To draw a rectangle, we'll use the following method to make four `Point` objects to represent the corners.\n",
        "\n",
        "要绘制一个矩形，我们将使用以下方法使四个 `Point` 对象来表示角。\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 27,
      "id": "8fb74cd8",
      "metadata": {},
      "outputs": [],
      "source": [
        "%%add_method_to Rectangle\n",
        "\n",
        "    def make_points(self):\n",
        "        p1 = self.corner\n",
        "        p2 = p1.translated(self.width, 0)\n",
        "        p3 = p2.translated(0, self.height)\n",
        "        p4 = p3.translated(-self.width, 0)\n",
        "        return p1, p2, p3, p4"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "20dbe0cb",
      "metadata": {},
      "source": [
        "Then we'll make four `Line` objects to represent the sides.\n",
        "\n",
        "那我们就做四个 `Line` 对象来表示侧面。\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 28,
      "id": "1c419c73",
      "metadata": {},
      "outputs": [],
      "source": [
        "%%add_method_to Rectangle\n",
        "\n",
        "    def make_lines(self):\n",
        "        p1, p2, p3, p4 = self.make_points()\n",
        "        return Line(p1, p2), Line(p2, p3), Line(p3, p4), Line(p4, p1)"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "30fe41cc",
      "metadata": {},
      "source": [
        "Then we'll draw the sides.\n",
        "\n",
        "然后我们画两边。\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 29,
      "id": "5ceccb7d",
      "metadata": {},
      "outputs": [],
      "source": [
        "%%add_method_to Rectangle\n",
        "\n",
        "    def draw(self):\n",
        "        lines = self.make_lines()\n",
        "        for line in lines:\n",
        "            line.draw()"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "390ba3e7",
      "metadata": {},
      "source": [
        "Here's an example.\n",
        "\n",
        "这里有一个例子。\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 30,
      "id": "2870c782",
      "metadata": {},
      "outputs": [],
      "source": [
        "make_turtle()\n",
        "line1.draw()\n",
        "line2.draw()\n",
        "box1.draw()"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "532a4f69",
      "metadata": {},
      "source": [
        "The figure includes two lines to represent the axes.\n",
        "\n",
        "该图包括两条线来表示轴。\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "0e713a90",
      "metadata": {},
      "source": [
        "## 更改矩形（Changing rectangles）\n",
        "\n",
        "Now let's consider two methods that modify rectangles, `grow` and `translate`.\n",
        "We'll see that `grow` works as expected, but `translate` has a subtle bug.\n",
        "See if you can figure it out before I explain.\n",
        "\n",
        "现在让我们考虑两种修改矩形的方法， `grow` 和 `translate` 。\n",
        "我们会看到 `grow` 按预期工作，但 `translate` 有一个微妙的错误。\n",
        "在我解释之前看看你能不能弄清楚。\n",
        "\n",
        "`grow` takes two numbers, `dwidth` and `dheight`, and adds them to the `width` and `height` attributes of the rectangle.\n",
        "\n",
        " `grow` 取两个数字， `dwidth` 和 `dheight` ,并将它们添加到 `width` 和 `height` 矩形的属性。\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 31,
      "id": "21537ed7",
      "metadata": {},
      "outputs": [],
      "source": [
        "%%add_method_to Rectangle\n",
        "\n",
        "    def grow(self, dwidth, dheight):\n",
        "        self.width += dwidth\n",
        "        self.height += dheight"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "a51913e2",
      "metadata": {},
      "source": [
        "Here's an example that demonstrates the effect by making a copy of `box1` and invoking `grow` on the copy.\n",
        "\n",
        "下面是一个示例，通过复制来演示效果 `box1` 并调用 `grow` 在副本上。\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 32,
      "id": "243ca804",
      "metadata": {},
      "outputs": [],
      "source": [
        "box2 = copy(box1)\n",
        "box2.grow(60, 40)\n",
        "print(box2)"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "6d74da62",
      "metadata": {},
      "source": [
        "If we draw `box1` and `box2`, we can confirm that `grow` works as expected.\n",
        "\n",
        "如果我们画 `box1` 和 `box2` ,我们可以确认 `grow` 按预期工作。\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 33,
      "id": "110f995f",
      "metadata": {},
      "outputs": [],
      "source": [
        "make_turtle()\n",
        "line1.draw()\n",
        "line2.draw()\n",
        "box1.draw()\n",
        "box2.draw()"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "0c940008",
      "metadata": {},
      "source": [
        "Now let's see about `translate`.\n",
        "It takes two numbers, `dx` and `dy`, and moves the rectangle the given distances in the `x` and `y` directions. \n",
        "\n",
        "现在让我们看看 `translate` 。\n",
        "它需要两个数字， `dx` 和 `dy` ,并将矩形移动给定的距离 `x` 和 `y` 方向。\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 34,
      "id": "8a5e7e73",
      "metadata": {},
      "outputs": [],
      "source": [
        "%%add_method_to Rectangle\n",
        "\n",
        "    def translate(self, dx, dy):\n",
        "        self.corner.translate(dx, dy)"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "c27fe91d",
      "metadata": {},
      "source": [
        "To demonstrate the effect, we'll translate `box2` to the right and down.\n",
        "\n",
        "为了演示效果，我们将翻译 `box2` 向右和向下。\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 35,
      "id": "78b9e344",
      "metadata": {},
      "outputs": [],
      "source": [
        "box2.translate(30, 20)\n",
        "print(box2)"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "e01badbc",
      "metadata": {},
      "source": [
        "Now let's see what happens if we draw `box1` and `box2` again.\n",
        "\n",
        "现在让我们看看会发生什么，如果我们画 `box1` 和 `box2` 再来一次.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 36,
      "id": "b70bcad9",
      "metadata": {},
      "outputs": [],
      "source": [
        "make_turtle()\n",
        "line1.draw()\n",
        "line2.draw()\n",
        "box1.draw()\n",
        "box2.draw()"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "5310bdd7",
      "metadata": {},
      "source": [
        "It looks like both rectangles moved, which is not what we intended!\n",
        "The next section explains what went wrong.\n",
        "\n",
        "看起来两个矩形都移动了，这不是我们的意图!\n",
        "下一节解释出了什么问题。\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "940adbeb",
      "metadata": {},
      "source": [
        "## 深层拷贝（Deep copy）\n",
        "\n",
        "When we use `copy` to duplicate `box1`, it copies the `Rectangle` object but not the `Point` object it contains.\n",
        "So `box1` and `box2` are different objects, as intended.\n",
        "\n",
        "当我们使用 `copy` 要复制 `box1` ,它会复制 `Rectangle` 对象，但不是 `Point` 它包含的对象。\n",
        "所以 `box1` 和 `box2` 是不同的对象，如预期的那样。\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 37,
      "id": "b67ce168",
      "metadata": {},
      "outputs": [],
      "source": [
        "box1 is box2"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "eac5309b",
      "metadata": {},
      "source": [
        "But their `corner` attributes refer to the same object.\n",
        "\n",
        "但是他们的 `corner` 属性引用同一个对象。\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 38,
      "id": "1d9cf4da",
      "metadata": {},
      "outputs": [],
      "source": [
        "box1.corner is box2.corner"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "f0cc51b5",
      "metadata": {},
      "source": [
        "The following diagram shows the state of these objects.\n",
        "\n",
        "下图显示了这些对象的状态。\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 39,
      "id": "6d883459",
      "metadata": {
        "tags": []
      },
      "outputs": [],
      "source": [
        "from diagram import Stack\n",
        "from copy import deepcopy\n",
        "\n",
        "binding_box1, frame_corner1 = make_rectangle_binding('box1', box1)\n",
        "binding_box2, frame_corner2 = make_rectangle_binding('box2', box2, dy=0.4)\n",
        "binding_box2.value.bindings.reverse()\n",
        "\n",
        "stack = Stack([binding_box1, binding_box2], dy=-1.3)"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 40,
      "id": "637042fb",
      "metadata": {
        "tags": []
      },
      "outputs": [],
      "source": [
        "from diagram import Bbox\n",
        "\n",
        "width, height, x, y = [2.76, 2.54, 0.27, 2.16]\n",
        "ax = diagram(width, height)\n",
        "bbox1 = stack.draw(ax, x, y)\n",
        "bbox2 = frame_corner1.draw(ax, x+1.85, y-0.6)\n",
        "bbox = Bbox.union([bbox1, bbox2])\n",
        "# adjust(x, y, bbox)"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "35f3e7e1",
      "metadata": {},
      "source": [
        "What `copy` does is called a **shallow copy** because it copies the object but not the objects it contains.\n",
        "As a result, changing the `width` or `height` of one `Rectangle` does not affect the other, but changing the attributes of the shared `Point` affects both!\n",
        "This behavior is confusing and error-prone.\n",
        "\n",
        "什么 `copy` 称为a **浅拷贝** 因为它复制对象而不是它包含的对象。\n",
        "因此，更改 `width` 或 `height` 的一个 `Rectangle` 不影响其他，但更改共享的属性 `Point` 两者都有影响!\n",
        "此行为令人困惑且容易出错。\n",
        "\n",
        "Fortunately, the `copy` module provides another function, called `deepcopy`, that copies not only the object but also the objects it refers to, and the objects *they* refer to, and so on. \n",
        "This operation is called a **deep copy**.\n",
        "\n",
        "幸运的是， `copy` 模块提供了另一个函数，称为 `deepcopy` ,它不仅复制对象，还复制它引用的对象，以及对象 *他们* 参考，等等。\n",
        "此操作称为 **深层拷贝** 。\n",
        "\n",
        "To demonstrate, let's start with a new `Rectangle` that contains a new `Point`.\n",
        "\n",
        "为了演示，让我们从一个新的 `Rectangle` 包含一个新的 `Point` 。\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 41,
      "id": "168277a9",
      "metadata": {},
      "outputs": [],
      "source": [
        "corner = Point(20, 20)\n",
        "box3 = Rectangle(100, 50, corner)\n",
        "print(box3)"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "ff9ee872",
      "metadata": {},
      "source": [
        "And we'll make a deep copy.\n",
        "\n",
        "我们会做一个深度拷贝。\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 42,
      "id": "75219ef6",
      "metadata": {},
      "outputs": [],
      "source": [
        "from copy import deepcopy\n",
        "\n",
        "box4 = deepcopy(box3)"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "7efd0e6a",
      "metadata": {},
      "source": [
        "We can confirm that the two `Rectangle` objects refer to different `Point` objects.\n",
        "\n",
        "我们可以确认两人 `Rectangle` 对象指的是不同的 `Point` 对象。\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 43,
      "id": "fde2486c",
      "metadata": {},
      "outputs": [],
      "source": [
        "box3.corner is box4.corner"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "ca925206",
      "metadata": {},
      "source": [
        "Because `box3` and `box4` are completely separate objects, we can modify one without affecting the other.\n",
        "To demonstrate, we'll move `box3` and grow `box4`.\n",
        "\n",
        "因为 `box3` 和 `box4` 是完全独立的对象，我们可以修改一个而不影响另一个。\n",
        "为了证明，我们将移动 `box3` 并成长 `box4` 。\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 44,
      "id": "3f6d1d6b",
      "metadata": {},
      "outputs": [],
      "source": [
        "box3.translate(50, 30)\n",
        "box4.grow(100, 60)"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "3ff31c7c",
      "metadata": {},
      "source": [
        "And we can confirm that the effect is as expected.\n",
        "\n",
        "我们可以确认效果是预期的。\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 45,
      "id": "092ded2c",
      "metadata": {},
      "outputs": [],
      "source": [
        "make_turtle()\n",
        "line1.draw()\n",
        "line2.draw()\n",
        "box3.draw()\n",
        "box4.draw()"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "67051d62",
      "metadata": {},
      "source": [
        "## 多态性（Polymorphism）\n",
        "\n",
        "In the previous example, we invoked the `draw` method on two `Line` objects and two `Rectangle` objects.\n",
        "We can do the same thing more concisely by making a list of objects.\n",
        "\n",
        "在前面的示例中，我们调用了 `draw` 方法上的两个 `Line` 对象和两个 `Rectangle` 对象。\n",
        "我们可以通过制作一个对象列表来更简洁地做同样的事情。\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 46,
      "id": "c846343c",
      "metadata": {},
      "outputs": [],
      "source": [
        "shapes = [line1, line2, box3, box4]"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "773955dd",
      "metadata": {},
      "source": [
        "The elements of this list are different types, but they all provide a `draw` method, so we can loop through the list and invoke `draw` on each one.\n",
        "\n",
        "此列表的元素是不同的类型，但它们都提供了 `draw` 方法，因此我们可以循环遍历列表并调用 `draw` 在每一个。\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 47,
      "id": "10912b62",
      "metadata": {},
      "outputs": [],
      "source": [
        "make_turtle()\n",
        "\n",
        "for shape in shapes:\n",
        "    shape.draw()"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "a1ae190c",
      "metadata": {},
      "source": [
        "The first and second time through the loop, `shape` refers to a `Line` object, so when `draw` is invoked, the method that runs is the one defined in the `Line` class.\n",
        "\n",
        "第一次和第二次通过循环， `shape` 指的是一个 `Line` 对象，所以当 `draw` 调用，则运行的方法是在 `Line` 类。\n",
        "\n",
        "The third and fourth time through the loop, `shape` refers to a `Rectangle` object, so when `draw` is invoked, the method that runs is the one defined in the `Rectangle` class.\n",
        "\n",
        "第三次和第四次通过循环， `shape` 指的是一个 `Rectangle` 对象，所以当 `draw` 调用，则运行的方法是在 `Rectangle` 类。\n",
        "\n",
        "In a sense, each object knows how to draw itself.\n",
        "This feature is called **polymorphism**.\n",
        "The word comes from Greek roots that mean \"many shaped\".\n",
        "In object-oriented programming, polymorphism is the ability of different types to provide the same methods, which makes it possible to perform many computations -- like drawing shapes -- by invoking the same method on different types of objects.\n",
        "\n",
        "从某种意义上说，每个对象都知道如何绘制自己。\n",
        "此功能称为 **多态性** 。\n",
        "这个词来自希腊词根，意思是 “许多形状”。\n",
        "在面向对象的编程中，多态性是不同类型提供相同方法的能力，这使得可以通过对不同类型的对象调用相同的方法来执行许多计算，例如绘制形状。\n",
        "\n",
        "As an exercise at the end of this chapter, you'll define a new class that represents a circle and provides a `draw` method.\n",
        "Then you can use polymorphism to draw lines, rectangles, and circles.\n",
        "\n",
        "作为本章末尾的练习，您将定义一个表示圆的新类，并提供一个 `draw` 方法。\n",
        "然后，您可以使用多态性来绘制线条，矩形和圆。\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "74d1b48f",
      "metadata": {},
      "source": [
        "## 调试（Debugging）\n",
        "\n",
        "In this chapter, we ran into a subtle bug that happened because we created a `Point` that was shared by two `Rectangle` objects, and then we modified the `Point`.\n",
        "In general, there are two ways to avoid problems like this: you can avoid sharing objects or you can avoid modifying them.\n",
        "\n",
        "在本章中，我们遇到了一个微妙的错误，因为我们创建了一个 `Point` 这是由两个共享 `Rectangle` 对象，然后我们修改了 `Point` 。\n",
        "一般来说，有两种方法可以避免这样的问题: 可以避免共享对象，也可以避免修改对象。\n",
        "\n",
        "To avoid sharing objects, you can use deep copy, as we did in this chapter.\n",
        "\n",
        "为了避免共享对象，您可以使用深层复制，就像我们在本章中所做的那样。\n",
        "\n",
        "To avoid modifying objects, consider replacing impure functions like `translate` with pure functions like `translated`.\n",
        "For example, here's a version of `translated` that creates a new `Point` and never modifies its attributes.\n",
        "\n",
        "为了避免修改对象，请考虑替换不纯的函数，如 `translate` 使用纯函数，如 `translated` 。\n",
        "例如，这里有一个版本的 `translated` 这将创建一个新的 `Point` 并且从不修改其属性。\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 48,
      "id": "c803c91a",
      "metadata": {},
      "outputs": [],
      "source": [
        "    def translated(self, dx=0, dy=0):\n",
        "        x = self.x + dx\n",
        "        y = self.y + dy\n",
        "        return Point(x, y)"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "76972167",
      "metadata": {},
      "source": [
        "Python provides features that make it easier to avoid modifying objects.\n",
        "They are beyond the scope of this book, but if you are curious, ask a virtual assistant, \"How do I make a Python object immutable?\"\n",
        "\n",
        "Python提供了更容易避免修改对象的功能。\n",
        "它们超出了本书的范围，但如果你好奇，可以问一个虚拟助手，“我如何让一个Python对象不可变？”\n",
        "\n",
        "Creating a new object takes more time than modifying an existing one, but the difference seldom matters in practice.\n",
        "Programs that avoid shared objects and impure functions are often easier to develop, test, and debug -- and the best kind of debugging is the kind you don't have to do.\n",
        "\n",
        "创建新对象比修改现有对象花费更多的时间，但是在实践中这种差异很少重要。\n",
        "避免共享对象和不纯函数的程序通常更容易开发、测试和调试 -- 最好的调试方式是你不必做的那种。\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "02106995",
      "metadata": {},
      "source": [
        "## 词汇表（Glossary）\n",
        "\n",
        "**shallow copy:**\n",
        "A copy operation that does not copy nested objects.\n",
        "\n",
        " **浅拷贝:** \n",
        "不复制嵌套对象的复制操作。\n",
        "\n",
        "**deep copy:**\n",
        "A copy operation that also copies nested objects.\n",
        "\n",
        " **深层复制:** \n",
        "也复制嵌套对象的复制操作。\n",
        "\n",
        "**polymorphism:**\n",
        "The ability of a method or operator to work with multiple types of objects.\n",
        "\n",
        " **多态性:** \n",
        "方法或操作符处理多种类型对象的能力。\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "09dd41c1",
      "metadata": {},
      "source": [
        "## 练习（Exercises）\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "32b151a5",
      "metadata": {
        "tags": []
      },
      "outputs": [],
      "source": [
        "# This cell tells Jupyter to provide detailed debugging information\n",
        "# when a runtime error occurs. Run it before working on the exercises.\n",
        "\n",
        "%xmode Verbose"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "da0aea86",
      "metadata": {},
      "source": [
        "### 询问虚拟助理（Ask a virtual assistant）\n",
        "\n",
        "For all of the following exercises, consider asking a virtual assistant for help.\n",
        "If you do, you'll want include as part of the prompt the class definitions for `Point`, `Line`, and `Rectangle` -- otherwise the VA will make a guess about their attributes and functions, and the code it generates won't work.\n",
        "\n",
        "对于以下所有练习，请考虑向虚拟助手寻求帮助。\n",
        "如果你这样做，你会希望包括作为提示的类定义的一部分 `Point` , `Line` ,和 `Rectangle` -- 否则VA会猜测它们的属性和功能，它生成的代码将无法正常工作。\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "7721e47b",
      "metadata": {},
      "source": [
        "### 练习（Exercise）\n",
        "\n",
        "Write an `__eq__` method for the `Line` class that returns `True` if the `Line` objects refer to `Point` objects that are equivalent, in either order.\n",
        "\n",
        "写一个 `__eq__` 方法的 `Line` 返回类 `True` 如果 `Line` 对象指 `Point` 等效的对象，以任意顺序排列。\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "2e488e0f",
      "metadata": {},
      "source": [
        "You can use the following outline to get started.\n",
        "\n",
        "您可以使用以下大纲开始。\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 49,
      "id": "92c07380",
      "metadata": {},
      "outputs": [],
      "source": [
        "%%add_method_to Line\n",
        "\n",
        "def __eq__(self, other):\n",
        "    return None"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 50,
      "id": "a99446c4",
      "metadata": {
        "tags": []
      },
      "outputs": [],
      "source": [
        "# Solution goes here"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "3a44e45a",
      "metadata": {},
      "source": [
        "You can use these examples to test your code.\n",
        "\n",
        "您可以使用这些示例来测试代码。\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 51,
      "id": "aa086dd1",
      "metadata": {},
      "outputs": [],
      "source": [
        "start1 = Point(0, 0)\n",
        "start2 = Point(0, 0)\n",
        "end = Point(200, 100)"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "e825f049",
      "metadata": {},
      "source": [
        "This example should be `True` because the `Line` objects refer to `Point` objects that are equivalent, in the same order.\n",
        "\n",
        "这个例子应该是 `True` 因为 `Line` 对象指 `Point` 相同顺序的等效对象。\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 52,
      "id": "857cba26",
      "metadata": {},
      "outputs": [],
      "source": [
        "line_a = Line(start1, end)\n",
        "line_b = Line(start2, end)\n",
        "line_a == line_b    # should be True"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "c3d8fb2b",
      "metadata": {
        "tags": []
      },
      "source": [
        "This example should be `True` because the `Line` objects refer to `Point` objects that are equivalent, in reverse order.\n",
        "\n",
        "这个例子应该是 `True` 因为 `Line` 对象指 `Point` 等效的对象，以相反的顺序。\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 53,
      "id": "b45def0a",
      "metadata": {},
      "outputs": [],
      "source": [
        "line_c = Line(end, start1)\n",
        "line_a == line_c     # should be True"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "8c9c787b",
      "metadata": {},
      "source": [
        "Equivalence should always be transitive -- that is, if `line_a` and `line_b` are equivalent, and `line_a` and `line_c` are equivalent, then `line_b` and `line_c` should also be equivalent.\n",
        "\n",
        "等价应该总是可传递的 -- 也就是说，如果 `line_a` 和 `line_b` 是等效的，并且 `line_a` 和 `line_c` 是等价的，那么 `line_b` 和 `line_c` 也应该是等效的。\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 54,
      "id": "9784300c",
      "metadata": {},
      "outputs": [],
      "source": [
        "line_b == line_c     # should be True"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "d4f385fa",
      "metadata": {},
      "source": [
        "This example should be `False` because the `Line` objects refer to `Point` objects that are not equivalent.\n",
        "\n",
        "这个例子应该是 `False` 因为 `Line` 对象是指 `Point` 不等效的对象。\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 55,
      "id": "5435c8e4",
      "metadata": {},
      "outputs": [],
      "source": [
        "line_d = Line(start1, start2)\n",
        "line_a == line_d    # should be False"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "0e629491",
      "metadata": {},
      "source": [
        "### 练习（Exercise）\n",
        "\n",
        "Write a `Line` method called `midpoint` that computes the midpoint of a line segment and returns the result as a `Point` object.\n",
        "\n",
        "写一个 `Line` 方法调用 `midpoint` 计算线段的中点，并将结果作为 `Point` 对象。\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "b8c52d19",
      "metadata": {},
      "source": [
        "You can use the following outline to get started.\n",
        "\n",
        "您可以使用以下大纲开始。\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 56,
      "id": "f377afbb",
      "metadata": {},
      "outputs": [],
      "source": [
        "%%add_method_to Line\n",
        "\n",
        "    def midpoint(self):\n",
        "        return Point(0, 0)"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 57,
      "id": "96d81b90",
      "metadata": {
        "tags": []
      },
      "outputs": [],
      "source": [
        "# Solution goes here"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "4df69a9f",
      "metadata": {},
      "source": [
        "You can use the following examples to test your code and draw the result.\n",
        "\n",
        "您可以使用以下示例测试代码并绘制结果。\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 58,
      "id": "0d603aa3",
      "metadata": {},
      "outputs": [],
      "source": [
        "start = Point(0, 0)\n",
        "end1 = Point(300, 0)\n",
        "end2 = Point(0, 150)\n",
        "line1 = Line(start, end1)\n",
        "line2 = Line(start, end2)"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 59,
      "id": "647d0982",
      "metadata": {},
      "outputs": [],
      "source": [
        "mid1 = line1.midpoint()\n",
        "print(mid1)"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 60,
      "id": "e351bea3",
      "metadata": {},
      "outputs": [],
      "source": [
        "mid2 = line2.midpoint()\n",
        "print(mid2)"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 61,
      "id": "5ad5a076",
      "metadata": {},
      "outputs": [],
      "source": [
        "line3 = Line(mid1, mid2)"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 62,
      "id": "8effaff0",
      "metadata": {},
      "outputs": [],
      "source": [
        "make_turtle()\n",
        "\n",
        "for shape in [line1, line2, line3]:\n",
        "    shape.draw()"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "0518c200",
      "metadata": {},
      "source": [
        "### 练习（Exercise）\n",
        "\n",
        "Write a `Rectangle` method called `midpoint` that find the point in the center of a rectangle and returns the result as a `Point` object.\n",
        "\n",
        "写一个 `Rectangle` 方法调用 `midpoint` 找到矩形中心的点，并将结果作为 `Point` 对象。\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "c586a3ed",
      "metadata": {},
      "source": [
        "You can use the following outline to get started.\n",
        "\n",
        "您可以使用以下大纲开始。\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 63,
      "id": "d94a6350",
      "metadata": {},
      "outputs": [],
      "source": [
        "%%add_method_to Rectangle\n",
        "\n",
        "    def midpoint(self):\n",
        "        return Point(0, 0)"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 64,
      "id": "de6756da",
      "metadata": {
        "tags": []
      },
      "outputs": [],
      "source": [
        "# Solution goes here"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "d186c84b",
      "metadata": {},
      "source": [
        "You can use the following example to test your code.\n",
        "\n",
        "您可以使用以下示例来测试代码。\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 65,
      "id": "4aec759c",
      "metadata": {},
      "outputs": [],
      "source": [
        "corner = Point(30, 20)\n",
        "rectangle = Rectangle(100, 80, corner)"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 66,
      "id": "7ec3339d",
      "metadata": {},
      "outputs": [],
      "source": [
        "mid = rectangle.midpoint()\n",
        "print(mid)"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 67,
      "id": "326dbf24",
      "metadata": {},
      "outputs": [],
      "source": [
        "diagonal = Line(corner, mid)"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 68,
      "id": "4da710d4",
      "metadata": {},
      "outputs": [],
      "source": [
        "make_turtle()\n",
        "\n",
        "for shape in [line1, line2, rectangle, diagonal]:\n",
        "    shape.draw()"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "00cbc4d9",
      "metadata": {},
      "source": [
        "### 练习（Exercise）\n",
        "\n",
        "Write a `Rectangle` method called `make_cross` that:\n",
        "\n",
        "写一个 `Rectangle` 方法调用 `make_cross` 那:\n",
        "\n",
        "1. Uses `make_lines` to get a list of `Line` objects that represent the four sides of the rectangle.\n",
        "\n",
        " 1. 用途 `make_lines` 获取的列表 `Line` 对象，这些对象表示矩形的四个边。\n",
        "\n",
        "2. Computes the midpoints of the four lines.\n",
        "\n",
        " 2. 计算四行的中点。\n",
        "\n",
        "3. Makes and returns a list of two `Line` objects that represent lines connecting opposite midpoints, forming a cross through the middle of the rectangle.\n",
        "\n",
        " 3. 制作并返回两个列表 `Line` 表示连接相对中点的线的对象，这些线通过矩形的中间形成十字形。\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "29e994c6",
      "metadata": {},
      "source": [
        "You can use this outline to get started.\n",
        "\n",
        "您可以使用此大纲开始。\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 69,
      "id": "30cc0726",
      "metadata": {},
      "outputs": [],
      "source": [
        "%%add_method_to Rectangle\n",
        "\n",
        "    def make_diagonals(self):\n",
        "        return []"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 70,
      "id": "6cdde16e",
      "metadata": {
        "tags": []
      },
      "outputs": [],
      "source": [
        "# Solution goes here"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "970fcbca",
      "metadata": {},
      "source": [
        "You can use the following example to test your code.\n",
        "\n",
        "您可以使用以下示例来测试代码。\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 71,
      "id": "2afd718c",
      "metadata": {},
      "outputs": [],
      "source": [
        "corner = Point(30, 20)\n",
        "rectangle = Rectangle(100, 80, corner)"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 72,
      "id": "b7bdb467",
      "metadata": {},
      "outputs": [],
      "source": [
        "lines = rectangle.make_cross()"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 73,
      "id": "9d09b2c3",
      "metadata": {},
      "outputs": [],
      "source": [
        "make_turtle()\n",
        "\n",
        "rectangle.draw()\n",
        "for line in lines:\n",
        "    line.draw()"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "0f707fe3",
      "metadata": {},
      "source": [
        "### 练习（Exercise）\n",
        "\n",
        "Write a definition for a class named `Circle` with attributes `center` and `radius`, where `center` is a Point object and `radius` is a number.\n",
        "Include special methods `__init__` and a `__str__`, and a method called `draw` that uses `jupyturtle` functions to draw the circle.\n",
        "\n",
        "为名为的类编写定义 `Circle` 带属性 `center` 和 `radius` ,在哪里 `center` 是一个Point对象，并且 `radius` 是一个数字。\n",
        "包括特殊方法 `__init__` 和a `__str__` ,和一个名为 `draw` 使用 `jupyturtle` 绘制圆的函数。\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "cb1b24a3",
      "metadata": {},
      "source": [
        "You can use the following function, which is a version of the `circle` function we wrote in Chapter 4.\n",
        "\n",
        "您可以使用以下函数，该函数是 `circle` 我们在第4章写的函数。\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 74,
      "id": "b3d2328f",
      "metadata": {},
      "outputs": [],
      "source": [
        "from jupyturtle import make_turtle, forward, left, right\n",
        "import math\n",
        "    \n",
        "def draw_circle(radius):\n",
        "    circumference = 2 * math.pi * radius\n",
        "    n = 30\n",
        "    length = circumference / n\n",
        "    angle = 360 / n\n",
        "    left(angle / 2)\n",
        "    for i in range(n):\n",
        "        forward(length)\n",
        "        left(angle)"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 75,
      "id": "189c30d4",
      "metadata": {},
      "outputs": [],
      "source": [
        "# Solution goes here"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "b4325143",
      "metadata": {},
      "source": [
        "You can use the following example to test your code.\n",
        "We'll start with a square `Rectangle` with width and height `100`.\n",
        "\n",
        "您可以使用以下示例来测试代码。\n",
        "我们将从一个正方形开始 `Rectangle` 带宽度和高度 `100` 。\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 76,
      "id": "49074ed5",
      "metadata": {},
      "outputs": [],
      "source": [
        "corner = Point(20, 20)\n",
        "rectangle = Rectangle(100, 100, corner)"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "2cdecfa9",
      "metadata": {},
      "source": [
        "The following code should create a `Circle` that fits inside the square.\n",
        "\n",
        "下面的代码应该创建一个 `Circle` 适合广场内部。\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 77,
      "id": "d65a9163",
      "metadata": {},
      "outputs": [],
      "source": [
        "center = rectangle.midpoint()\n",
        "radius = rectangle.height / 2\n",
        "\n",
        "circle = Circle(center, radius)\n",
        "print(circle)"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "37e94d98",
      "metadata": {},
      "source": [
        "If everything worked correctly, the following code should draw the circle inside the square (touching on all four sides).\n",
        "\n",
        "如果一切正常，下面的代码应该绘制正方形内的圆圈 (触摸所有四边)。\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 78,
      "id": "e3b23b4d",
      "metadata": {},
      "outputs": [],
      "source": [
        "make_turtle(delay=0.01)\n",
        "\n",
        "rectangle.draw()\n",
        "circle.draw()"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "a7f4edf8",
      "metadata": {
        "tags": []
      },
      "source": [
        "[Think Python: 3rd Edition](https://allendowney.github.io/ThinkPython/index.html)\n",
        "\n",
        "Copyright 2024 [Allen B. Downey](https://allendowney.com)\n",
        "\n",
        "版权2024 [Allen B. Downey](https://allendowney.com) \n",
        "\n",
        "Code license: [MIT License](https://mit-license.org/)\n",
        "\n",
        "代码许可证: [MIT License](https://mit-license.org/) \n",
        "\n",
        "Text license: [Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International](https://creativecommons.org/licenses/by-nc-sa/4.0/)\n",
        "\n",
        "文本许可证: [Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International](https://creativecommons.org/licenses/by-nc-sa/4.0/) \n",
        "\n"
      ]
    }
  ],
  "metadata": {
    "celltoolbar": "Tags",
    "kernelspec": {
      "display_name": "Python 3 (ipykernel)",
      "language": "python",
      "name": "python3"
    },
    "language_info": {
      "codemirror_mode": {
        "name": "ipython",
        "version": 3
      },
      "file_extension": ".py",
      "mimetype": "text/x-python",
      "name": "python",
      "nbconvert_exporter": "python",
      "pygments_lexer": "ipython3",
      "version": "3.10.11"
    }
  },
  "nbformat": 4,
  "nbformat_minor": 5
}
